package org.fjsei.yewu.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.fjsei.yewu.config.datasource.CockroachDBHealthIndicator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;


//【发现】数据库的瓶颈是IO性能，特别是磁盘非易失性，持久性存储，的存储性能；同步写一致性？储。

/** 这里逻辑真麻烦，各种可能性。
 * CRDB数据库对CPU要求相当的高! 查询实际有自带缓存的能力；并发争用问题，死锁，IO性能要求高，网络延迟和延迟时间波动性要求。
 * Raft分布式一致性算法，节点有三种角色 Leader：Candidate：Follower。
 * 为何选择小强数据库的？看上其扩展性支持水平线性扩展，磁盘损坏灾难恢复能力，分布式事务强一致性，可用SSD存储介质，数据分片无需应用层关心。
 * 可支持最大4EB数据，支持跨地域部署，去中心化，自动水平扩展，自动故障恢复；CockroachDB选择了range base分片方式，在线扩容。
 * 华为云数据库 GaussDB 与之共同点：share nothing架构，无锁并发事务，etcd集群基于raft；不同点：单分片版本源代码开源，支持4PB存储。
 * 云原生分布式数据库 CockroachDB 小强 CRDB问题：
 * 三个节点宕机2个后无法继续死等恢复；
 * url连接必须指定某个IP和端口的，生产环境：端口不一样变，IP域名也不能动，配置特定指定一个节点宕机就无法访问。
 * 开源数据库用GoLang写的,Raft： https://github.com/cockroachdb/cockroach    https://www.cockroachlabs.com/docs/releases/index.html
 必须写上ssl证书路径名字；192.168.171.3:26257实际是负载均衡器地址。
 url: jdbc:postgresql://192.168.171.3:26257/seipf?ssl=true&sslmode=verify-full&sslrootcert=D:/ca.crt&sslcert=D:/client.herzhang.crt&sslkey=D:/client.herzhang.pk8&sslpassword=
 * 可跨多个集群部署a single, multi-region CockroachDB cluster running on Kubernetes.； private Kubernetes cluster；
 * Manual GCE:自己弄manual GCE cluster 谷歌的GCE云服务器； Hosted GKE:是商业云服务，GKE cluster云服。
 * win10 virtualbox 安装kubernetes集群； Centos7.6搭建k8s环境；running CockroachDB on Kubernetes: # Linux kernel ；
 * k8s集群 Kubernetes； Operator；--listen-addr 默认，配置--advertise-addr= ；
 * Use SSDs instead of traditional HDDs. 不建议使用网络连接的块存储。
 * Deploy CockroachDB manually or use an orchestration system like Kubernetes生产环境, Start a cluster locally测试。
 * Deploy CockroachDB On-Premises 本地。 TCP 26257  8080 单机器运行1节点； multiple 机房吗availability zones: 跨地域 multiple regions:；
 * 单个zone可能有多个node； NTP service； using load balancer, 多个负载平衡器实例 HAProxy；
 * using systemd. #数据库部署支持： Kubernetes部署模式（k8s）， Deploy CockroachDB On-Premises人工集群部署模式(linux命令+load balancer配置文件)。
 * 数据分片是自动进行，没有路由的说法，按照字段实际取值调整, 列存K/V存储？。
 * 大多数用户不需要直接使用分区(企业版)。应使用CockroachDB内置的多区域功能，自动处理分区。地理分区,归档分区;涉及主键(range+id)二级索引。configure non-partitioned table to be GLOBAL table.
 * PARTITION BY LIST (country)(, ,); PARTITION BY RANGE (graduation_date)(PARTITION graduated VALUES FROM (MINVALUE) TO ('2017-08-15'),PARTITION current VALUES FROM ('2017-08-15') TO (MAXVALUE));
 * Sharding 分片：CockroachDB自动切分数据，作为其分布式数据库体系结构的一部分。
 * 默认是Zone failure幸存。用以下语句开启Region failure幸存，代价是以较慢的写入性能（由于网络跳数 @TCP延迟@）,写入延迟将至少增加到最近区域Region的来回传输时间,读取性能不受影响;最少配3个Region。
 * 禁用Linux内存交换，--cache=.35 --max-sql-memory=.35合计最多75%RAM; --cache默认128MB。
 * CockroachDB总用可序列化隔离级别，READ COMMITTED若是没有其他措施配套的话，会导致并发导致的逻辑错误。
 * ACIDRain：发现事务Bug不严谨； 小强数据库只提供SERIALIZABLE隔离级别的：     https://www.cnblogs.com/ivan-uno/p/8257672.html
 * 普通隔离级别RC 读已提交 级别存在 write skew 问题，是漏洞，所以不能使用的。 乐观锁防止更新丢失问题，但是 write skew 不是同一个漏洞的，是逻辑前提条件不可重复读两次读判定相反引起的，提交也不是同一条记录更新。
 * This is why we recommend keeping transactions as small as possible.
 * IDEA Database工具窗开启连接：可能导致CRDB事务运行速度爆缓慢，要掐掉连接。客户连接CRDB集群也直接针对单个Database的。
 * 【限制】JPA也不能跨Database吧; 跨越Database界线在DB的Cluster内做外键关联：被CRDB明确禁止。sql.cross_db_fks.enabled默认false; 定位不同:Schema层次。
 * 问题编程模型变化大，事务，争用激烈，要求小事务化，非索引字段不要用于过滤用途避免全面扫描。  https://www.cockroachlabs.com/docs/stable/transactions.html
 * JPA Hibernate家族实际上对 CockroachDB 当做特例处理，有别于传统关系数据库的。 NewSQL; 分布式。
 * 数据库停机直接拷贝文件，【同步或快照数据库思考】CRDB强制停机，短暂让全网服务掉线，立刻把集群数据库的文件data目录全部备份，然后立刻重新启动CRDB集群，备份速度很多应用层恢复也很快。
 * 或者考虑全网业务层短暂时间内只能读取但是不能够更改数据库的表数据，其它步骤同上面的，只读不改不新增，然后快速恢复？短暂时间只能让修改新增业务不停转圈圈等待。？还不如全部休息一会儿更加稳妥！。
 * 这个?全体事务都被挂起：等同于一个超级事务block全部写事务。全部超时异常，正常select只读也是被阻塞掉了。
 * 尽量瘦身集群的存储文件大小？数据库表的数据瘦身？历史数据尽量别装入，日志流水数据尽量简短或避免掉，data目录越大拷贝越慢，快照停机时间不可忍受的。非事务数据剥离出去？多数据源也不行多服务端挂钩出现不一致！
 * */

@Component
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class RetryableTransactionAspect {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    // 注入健康指示器
    private final CockroachDBHealthIndicator healthIndicator;
    //极端情况允许对前端透明的 自动 尝试次数。
    private final int retryAttempts = 20;

    // 通过构造函数注入
    public RetryableTransactionAspect(CockroachDBHealthIndicator healthIndicator) {
        this.healthIndicator = healthIndicator;
    }
    private boolean isDatabaseAvailable() {
        try {
            Health health = healthIndicator.health();
            return health.getStatus().equals(Status.UP);
        } catch (Exception e) {
            logger.warn("Database availability check failed: {}", e.getMessage());
            return false;
        }
    }

    /**
     * AOP的基本概念 Pointcut(切入点) @Aspect(切面); Advice @Around： 环绕增强，相当于MethodInterceptor.
     */
    @Pointcut("(execution(* md..*(..)) || execution(* org.fjsei.yewu..*(..)) ) && @annotation(transactional)")
    public void anyTransactionBoundaryOperation(Transactional transactional) {
    }

    /**
     * 数据库CRDB带来特别处理机制 : contention; client-side retry handling.
     */
    @Around(value = "anyTransactionBoundaryOperation(transactional)",
            argNames = "pjp,transactional")
    public Object doInTransaction(ProceedingJoinPoint pjp, Transactional transactional)
            throws Throwable {
        // 在事务开始前检查数据库可用性
        if (!isDatabaseAvailable()) {
            throw new ConcurrencyFailureException("Database cluster is unavailable - maintenance mode active");
        }
        final Instant callTime = Instant.now();
        boolean noRetry = Arrays.asList(transactional.label()).contains("noRetry");
        if(noRetry)   return  pjp.proceed();      //不是CockroachDB不需要重试事务

        final int timeout = transactional.timeout();
        long backoffMillis = 0;
        int numCalls = 0;

        do {
            try {
                numCalls++;
                if(numCalls > 1) {
                    try {
                        if(numCalls > 2) {
                            Duration duration = Duration.between(callTime, Instant.now());
                            if (timeout > 0 && timeout < (duration.getSeconds() + (backoffMillis / 1000)) )
                                break;       //超时放弃
                        }
                        Thread.sleep(backoffMillis);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }

                // 每次重试前都检查数据库可用性
                if (!isDatabaseAvailable()) {
                    throw new ConcurrencyFailureException("Database cluster became unavailable during transaction retry");
                }

                Object rv = pjp.proceed();

                if (numCalls > 1) {
                    logger.debug(
                            "完成,做" + numCalls + "次 of " + retryAttempts + "最多 ("
                                    + Duration.between(callTime, Instant.now()).toString() + ")");
                }
                return rv;

            } catch (UndeclaredThrowableException ex) {
                Throwable t = ex.getUndeclaredThrowable();
                while (t instanceof UndeclaredThrowableException) {
                    t = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
                }
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) {
                        backoffMillis = handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;

            } catch (TransientDataAccessException | TransactionSystemException ex) {
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) {
                        backoffMillis = handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;

            } catch (CannotCreateTransactionException ex) {
                backoffMillis = handleTransientException(ex, numCalls, pjp.getSignature().toShortString());
                continue;

            } catch (JpaSystemException ex) {
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) {
                        backoffMillis = handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        logger.debug("JpaSystem重试=" + pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;

            } catch (Exception ex) {
                throw ex;

            } catch (Error ex) {
                Thread.sleep(500);
                throw ex;
            }

        } while (numCalls < retryAttempts);

        throw new ConcurrencyFailureException("放弃(" + numCalls + ") for method ["
                + pjp.getSignature().toShortString() + "]. Giving up!");
    }

    /**
     * 失败后稍微等一下，重复提交事务
     */
    private long handleTransientException(Exception ex, int numCalls, String method) {
        long backoffMillis;
        if(numCalls <= 1)     backoffMillis = 20;
        else if(numCalls <= 2)   backoffMillis = 300;
        else if(numCalls <= 5)   backoffMillis = 200 + (long)(Math.random() * 1000);
        else if(numCalls == 6)   backoffMillis = 3000;
        else if(numCalls <= 10)   backoffMillis = 7000;
        else if(numCalls == 11)   backoffMillis = 4000;
        else if(numCalls <= 15)   backoffMillis = 8000;
        else if(numCalls == 17)   backoffMillis = 5000;
        else    backoffMillis = 9000;

        if (logger.isDebugEnabled()) {
            logger.debug("临时异常 (backoff {}ms) in call {} to '{}': {}",
                    backoffMillis, numCalls, method, ex.getMessage());
        }
        return backoffMillis;
    }
}

/*
CockroachDB可序列化 SERIALIZABLE 隔离中实现，是SQL标准中最强的隔离级别。
Raft 算法在斯坦福 Diego Ongaro f和 John Ousterhout 于 2013 年发表的《In Search of an Understandable Consensus Algorithm》中提出。相较于 Paxos，
Raft通过逻辑分离使其更容易理解和实现，目前，已经有十多种语言的 Raft 算法实现框架，较为出名的有 etcd、Consul 。
Cockroach 提供快照隔离 (SI) 和 串行快照隔离 (SSI) 语义，此语义允许 外部一致性、无锁读写。 两者都基于历史快照时间戳和当前时间。
SI提供无锁读写但是会有写偏序问题(由于每个事务在更新过程中无法看到其他事务的更改的结果，导致各个事务提交之后的最终结果违反了一致性);
SSI消除了写偏序，但在有争议系统的某些场景下会损失性能。SSI是默认的隔离机制。
 2PC 协议；SI语义事务读操作不会阻塞；SSI语议事务读操作会中止；比两阶段事务提交协议的时延更低。因为第二阶只需要写一次事务表，不需要等待所有事务参与者。
Spanner必须要求精确的时间信号，
所有顶层的元数据保存在一个range里（第一个range）. 一个元数据是256字节，一个64M的range可以保存 2\^18 = 256K个range的元数据，单级寻址可以提供64M * 2\^18 =16T的存储空间。
两级索引 ，第一级用来保存第二级的地址，第二级用来保存数据。 采用两级寻址，支持2\^(18 + 18) = 64G个range, 每个range支持寻址2\^26 B = 64M ，则总共可寻址2\^(36+26) B = 2\^62 B = 4E空间.
the number of nodes dictates the number of heartbeats ； LevelDB/RocksDB是更实用的LSM实现之一；
副本数：默认3个副本。大公司大平台可设为5；
【节点数问题】CockroachDB没有理论上的扩展限制，实际上，它可以在256个节点上实现近似线性的性能。没有节点数限制 #但节点数越多会导致延迟越大。
性能由TPC-C吞吐率衡量，单位是tpmC。tpm是transactions per minute的简称；C指TPC中的C基准程序。是每分钟内系统处理的新订单个数。
官方集群测试=81台CentOS_x86,36vCPU72GB,10Gbps机器,最大tpmC性能=1,684,437个；每节点347订单/秒。
我本机笔记本3个节点服务器 4vCPU2.3Ghz8GBMem+SSD；关键的tpmC=854.3; 磁盘是瓶颈。测试结果比较可信。
DDR5内存实测读取带宽超过90GB/s,写入带宽接近89GB/s,拷贝带宽接近78GB/s。CPU:L3缓存494GB/s,L2缓存987GB/s,L1缓存3422GB/s;内存DRAM随机读写性能损失较小，NVMe SSD存储随机读写性能损失大。
针对数据库来讲，基本都是随机读写的, 此时预计Mem内存比SSD Disk存储速度要快100倍！。
阿里OceanBase以7.07亿tpmC,基于Paxos,1557台节点84vCPU336GB,25Gbps带宽400万PPS；按此比对同等配置Cockroach顶多不超过1.8亿tpmC；对比小强X3.92倍速率？测试基准问题？磁盘、事务波及节点面分区策略。
OceanBase硬件要求8C+32G,NTP同步;主备?备集群个数限制31个;备集群弱一致性读服务;TEXT最大65535字节;主分区具备强一致性读和写能力，备分区具备弱一致性读能力。只读型非Paxos副本;
OceanBase数据库:多版本Read Committed,多级缓存?可靠性？;支持集群节点超过1500台，最大单集群数据量超过3PB; 和Cockroach测试的事务隔离级别不同。
Cockroach数据库：JSONB建议<1MB; 尽量上Inner joins表链接；
#传统的ID自增长顺序Long在分布式系统中无法很好工作,会导致“热点”节点，并造成性能瓶颈。id改成UUID是必须的。id UUID DEFAULT gen_random_uuid();
建议用UUID。而unique_rowid()性能较差最好别用它；递增号码可用单独表计数器事务化手动生成。传统SEQUENCE性能最慢。@SequenceGenerator列不一定是@ID;@GenericGenerator(name = "IDGenerator", strategy = "identity")也类似;
unique_rowid()对应64位不能映射Integer的Java类型要用Long而且不是递增的整数啊！。
计数器实现模式1：要求不出现跳号间隙或漏过某些数值的，性能严重受限：CREATE TABLE cnt(val INT PRIMARY KEY); INSERT INTO cnt(val) VALUES(1); INSERT INTO cnt(val) SELECT max(val)+1 FROM cnt RETURNING val;的模式。
计数器实现模式2：允许出现漏掉某些数字号码，跳号可以被容忍的情况：SEQUENCE也会有调号。更新事务对某个计数器字段直接 counter++；事务保证唯一性获取编号给用户。事务回滚的？
快照隔离（SI,Snapshot Isolation）：MVCC和锁都是SI的重要实现手段，时间戳是生成SI的关键要素。SI还无法解决Write Skew写偏序; 分布式系统一时钟就成为一个复杂问题;
完全的串行化隔离（Serializable Isolation）=S2PL模式Strict Two-Phase Locking无法应用于实际生产环境。直到SSI的出现，人们终于找到具有实际价值的串行化隔离方案。SSI, Serializable Snapshot Isolation序列化快照是基于SI改进达到Serializable级别的隔离性。
SSI保留了SI的很多优点，特别是读不阻塞任何操作，写不会阻塞读。事务依然在快照中运行，但增加了对事务间读写冲突的监控用于识别事务图（transaction graph）中的危险结构。
虽然会导致某些事务的错误回滚（不会导致anomaly的事务被误杀），但可确保消除anomaly。    SSI性能接近SI，远远好于S2PL模式。
CockroachDB实现SSI并将其作为其默认隔离级别。串行化快照隔离SSI带来的好处：无需开发人员在代码通过#显式锁#来避免异常，从而降低了人为错误的概率。
console联机操作CockroachDB翻页滚动也会抛出异常，要手写offset limit,排序深度翻页性能越深的越差。长时事务批处理作业?: Checkpoint? Cursor?
CRDB的SSI探秘：插入新记录会串行就会阻碍它人读取请求，但是更新旧的记录却不会阻碍它人读取请求，但问题是更新记录速度更慢，插入新记录速度更快的。OLTP更新多于插入吧，为读取优化。
插入新记录的SSI事务并不会阻碍它人的以ID为定位的findById()请求; 实体表若是属于插入新记录很频繁的表，应该尽量避免用不是ID的字段来做过滤查找的？带有插入新记录的事务必须尽量短暂必须小于10秒；
只要有生成实体新记录的接口函数都应当尽量急速提交，事务时间长了就会阻碍它人的对新记录对应的那一个实体表查询的过滤请求；尽量业务层面分解事务函数，能拆解的就拆解，事务小型化；业务层弥补合并和事务外失败逻辑。
针对CockroachDB优化：尽量是Update,少用Insert Delete,事务Insert会阻碍其他人除了ID查询之外其余查询过滤统计等操作。Update语句就不会有这个阻塞并发的问题，但是Update比Insert执行更慢点。
数据库Vitess 支持单个分片内的事务，不支持跨分片事务。 数据库TiDB不支持外键,不支持串行化隔离级别;
YugaByte数据库是对手        https://copyfuture.com/blogs-details/20210727213025980v
cockroachDB的解密    https://www.zhihu.com/question/429946482/answer/1602639985
YugaByte的分布式事务原子性实现 与cockroachDB 旧版本类似, 都是2PC 变种，只是cockroachDB 新版本做了优化，变成了1PC ,性能估计是目前所有开源New-SQL 的好几倍, 后期YugaByte也会跟进优化,用C++实现;
CockroachDB和YugabyteDB交锋,TiDB类似  https://www.zhihu.com/question/449949351
分布式数据库 叫法=NewSQL;   https://baijiahao.baidu.com/s?id=1699078374020499984&wfr=spider&for=pc
加version乐观锁实际对CRDB没用吗?? CRDB数据库的内置事务隔离等级更高。IDEA的数据库工具会无意识添加事务很可能导致后端程序事务超慢或失败，必须重启动IDEA。
IDEA Database工具通过console当前数据库，跨数据库查询另外一个数据库的表的情况，可能死锁？ 同一个数据库之内的事务没有这种问题。
CockroachDB 数据分区 GEO-Partitioning 同城双机房 3地多中心 https://www.sohu.com/a/326958845_411876
BACKUP RESTORE 集群备份恢复功能 https://www.cockroachlabs.com/docs/v21.2/backup
Backup and restore with userfile没用云的不能搞集群级备份 https://www.cockroachlabs.com/docs/v21.2/use-userfile-for-bulk-operations#backup-and-restore-with-userfile
集群证书权限用户角色等设置是属于集群级别备份，其它都是database级别备份多个库多个命令文件Job。cockroach userfile get; upload; cockroach userfile delete;
ZNBase(基于 CockroachDB 来) 和 TiDB 都是既有行存的数据副本，也有列存的数据副本; TiDB不支持外键；
临时备份: INSERT INTO isp_saved1 SELECT * FROM isp;
drop table isp cascade;只能删除isp却无法把eqp关联的字段清空所以后续报错！
性能严重影响：https://www.cockroachlabs.com/docs/stable/delete.html 非全表删除情况：CRDB数据库删除数据性能考虑： 过滤字段必须建立索引。可重做的小事务分批次受限大小的记录数删除。最好排序获取ID范围，例子如下：
 DELETE  from public.storesync where fail='OK' AND id<='20dbf4b0-a0d5-4be1-bd85-43dc4c4fbb2f'
 ORDER BY id DESC LIMIT 5000  RETURNING id,cod,oid,fail;  修改id<=''取值，多次执行删除，最后检查是否条件满足所有的记录都已删除。
免费版: CRDB非enterprise license版 PARTITION BY无法做: Zone Multi-region功能；
在SELECT查询返回的主键值上以小于初始SELECT批大小的批编写嵌套的DELETE循环。如果可能,建议每个循环要开启新的单独的事务中执行独立一次DELETE;把Select独立于Delete事务之外的先前查询。
人工拆解事务：成了用户负责的。实际是丢失大的事务特性，只是保障了小事务！最好带上条件select Order by ID主键，看似矛盾：最好合并多个delete为单一条delete语句 in[ids..]。
批量非独立定位的有索引的情形做法：DELETE FROM new_order WHERE no_w_id <= %s ORDER BY no_w_id DESC LIMIT 5000 RETURNING no_w_id；最好被过滤列必须有索引！
?单一个事务中：前面删除了某id，后面继续查询该表和再做删除其他id，性能问题。
删除性能差：批量数据删除过滤字段有索引，过滤字段没索引的2个情景；另外还有JPA复杂关联删除id关联多个表的情况也会一次性批量删除List集合关联的，需要关联ID都做外键约束带上索引。
CRDB的core版本； Liquibase数据库变更版本管理Spring集成、Gradle插件； JOOQ适合查询？=QueryDsl配套JPA对比 | JPA Specification toPredicate CriteriaBuilder；
Multi-Region Cluster: 服务器跨越长距离直接网络延迟很大了，跨地域-Region，要求为每张表隐含地增加地理city_id字段来区分存储地域，没强制查询必须有地理信息，没有那就全局搜。
【关键要求】 批量更新要拆分小的事务：在单独的只读事务中运行选择查询。这有助于减少事务争用。使用LIMIT子句将查询的行数限制为要更新的行的子集。 https://www.cockroachlabs.com/docs/stable/bulk-update-data.html
【严重影响性能】 删除一堆数据如何分解小事务：请在筛选列上添加ORDER BY子句。用LIMIT子句，非索引全表搜的MVCC时间等待-5秒  https://www.cockroachlabs.com/docs/stable/bulk-delete-data.html
CRDB执行select * from request where status='FIN' and (mdtime-now())<-(mdays*3600*24)::interval这里的时间相减差距是秒为单位的，now()不是本地时间要滞后-8小时的。
磁盘实在太缓慢情况：WARNING: disk slowness detected: unable to sync log files within 10s
* */
